/* ===================

AX2E (AXESCON XACML 2.0 Engine) is the Java authorization engine, which implements OASIS XACML 2.0 standard.
Copyright (C) 2007 AXESCON LLC

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA


Contact: AXESCON LLC - info{at}axescon.com

 =================== */
package axescon.xacml.engine;

import axescon.xacml.FeatureDisabler;
import axescon.xacml.api.*;
import axescon.xacml.api.alg.PolicyCombAlgFactory;
import axescon.xacml.api.alg.RuleCombAlgFactory;
import axescon.xacml.engine.alg.BasePolicyCombAlgFactory;
import axescon.xacml.engine.alg.BaseRuleCombAlgFactory;
import axescon.xacml.model.ctx.*;
import axescon.xacml.model.ctx.impl.ResultImpl;
import axescon.xacml.model.ctx.impl.StatusImpl;
import axescon.xacml.model.ctx.impl.ResponseImpl;
import axescon.xacml.model.policy.*;
import axescon.xacml.model.shared.Decision;

import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author argyn
 *         Date: Jun 25, 2005
 *         Time: 2:16:46 PM
 *         Responsibilities: represents XACML PDP
 *         This should implement axescon.xacml 2.0 spec, ch. 7.10. Policy evaluation
 *         <p/>
 *         Supports multiple resources.
 */
public class PolicyEvaluatorImpl implements PolicyEvaluator {

    private Logger logger = Logger.getLogger(this.getClass().getName());

    public PolicyEvaluatorImpl(RuleCombAlgFactory ruleCombAlgFactory, PolicyCombAlgFactory policyCombAlgFactory, TargetMatcherImpl targetMatcher, RuleEvaluator ruleEvaluator) {
        this.ruleCombAlgFactory = ruleCombAlgFactory;
        this.policyCombAlgFactory = policyCombAlgFactory;
        this.targetMatcher = targetMatcher;
        this.ruleEvaluator = ruleEvaluator;
    }

    public PolicyEvaluatorImpl() throws XacmlException {
        this(false);
    }

    public void setDefaults() throws XacmlException {
        setPolicyCombAlgFactory(new BasePolicyCombAlgFactory(true));
        setRuleCombAlgFactory(new BaseRuleCombAlgFactory(true));

        FnFactory fnFac = new BaseFnFactory(true);
        ExpressionEvaluatorImpl ee = new ExpressionEvaluatorImpl();
        ee.setFnFactory(fnFac);

        TargetMatcherImpl tm = new TargetMatcherImpl();
        tm.setFnFactory(fnFac);
        tm.setExpressionEvaluator(ee);

        setTargetMatcher(tm);

        RuleEvaluator re = new RuleEvaluator();
        re.setExpressionEvaluator(tm.getExpressionEvaluator());
        re.setTargetMatcher(tm);

        setRuleEvaluator(re);
    }

    public PolicyEvaluatorImpl(boolean setDefaults) throws XacmlException {
        if (setDefaults) setDefaults();
    }

    /**
     * implements spec v.2.0 ch.7.10
     *
     * @param p
     * @param req
     * @param ch
     * @return
     */
    public Result eval(Policy p, XacmlCtx req, CtxHandler ch) throws XacmlException {

        // below is the implementation of policy truth table T 5 in ch7.10
        try { // lines 1-3 of truth table (TT)
            boolean tmatched = getTargetMatcher().match(p.getTarget(), req, ch);
            if (tmatched) {
                return evalMatched(p, req, ch);
            } else { // line 4 of truth table
                ResultImpl ret = new ResultImpl();
                ret.setDecision(Decision.BYTE_NOT_APPLICABLE);
                return ret;
            }
        } catch (TargetMatchIndeterminateException e) {// line 5 of truth table
            ResultImpl ret = new ResultImpl();
            ret.setDecision(Decision.BYTE_INDETERMINATE);
            return ret;
        }
        //return null;
    }

    /**
     * this evaluates a policy, assuming that its target has been matched already
     * implements spec v.2.0 ch.7.10
     *
     * @param p
     * @param req
     * @param ch
     * @return
     */
    public Result evalMatched(Policy p, XacmlCtx req, CtxHandler ch) throws XacmlException {
        Rule[] rules = p.getRuleArray();

        Result ret = combine(rules, req, ch, p.getRuleCombiningAlgId(), p.getRuleCombinerParametersArray());
        if (ret.getDecision() != Decision.BYTE_INDETERMINATE && ret.getDecision() != Decision.BYTE_NOT_APPLICABLE) {
            ret.addObligationArray(p.getObligationArray());
        }
        return ret;
    }


    public Result combine(Rule[] rules, XacmlCtx req, CtxHandler ch, String combAlg, RuleCombinerParameters[] params)
            throws XacmlException {
        return getRuleCombAlgFactory().getRuleCombAlg(combAlg).combine(rules, req, ch, params, this);

    }

    public Result combine(PolicyProxy[] ps, XacmlCtx req, CtxHandler ch,
                          String combAlg, PolicyCombinerParameters[] params) throws XacmlException {

        return getPolicyCombAlgFactory().getPolicyCombAlg(combAlg)
                .combine(ps, req, ch, params, this);

    }

    private RuleCombAlgFactory ruleCombAlgFactory;

    public RuleCombAlgFactory getRuleCombAlgFactory() {
        return ruleCombAlgFactory;
    }

    private PolicyCombAlgFactory policyCombAlgFactory;

    public void setRuleCombAlgFactory(RuleCombAlgFactory ruleCombAlgFactory) {
        this.ruleCombAlgFactory = ruleCombAlgFactory;
    }

    private TargetMatcher targetMatcher;

    public TargetMatcher getTargetMatcher() {
        return targetMatcher;
    }

    public void setTargetMatcher(TargetMatcher targetMatcher) {
        this.targetMatcher = targetMatcher;
    }

    private RuleEvaluator ruleEvaluator;


    public RuleEvaluator getRuleEvaluator() {
        return ruleEvaluator;
    }

    public void setRuleEvaluator(RuleEvaluator ruleEvaluator) {
        this.ruleEvaluator = ruleEvaluator;
    }

    /**
     * implements spec v.2.0 ch.7.11
     *
     * @param p
     * @param req
     * @param ch
     * @return
     */
    public Result eval(PolicySet p, XacmlCtx req, CtxHandler ch) {

/*
        // below is the implementation of policy truth table T 5 in ch7.10
        try { // lines 1-3 of truth table (TT)
            boolean tmatched =getTargetMatcher().match(p.getTarget(), req, ch);
            if (tmatched) {
                return evalMatched(p, req, ch);
            } else { // line 4 of truth table
                ResultImpl ret = new ResultImpl();
                ret.setDecision(Decision.BYTE_NOT_APPLICABLE);
                return ret;
            }
        } catch (TargetMatchIndeterminateException e) {// line 5 of truth table
            ResultImpl ret = new ResultImpl();
            ret.setDecision(Decision.BYTE_INDETERMINATE);
            return ret;
        }
*/
        return null;
    }

    /**
     * this evaluates a policy set, assuming that its target has been matched already
     * implements spec v.2.0 ch.7.11
     *
     * @param p
     * @param req
     * @param ch
     * @return
     */
/*
    public Result evalMatched(PolicySet p, XacmlCtx req, CtxHandler ch) {
        List<PolicyObject> pos = p.getPolicyList();
        List<PolicyObject> pos = p.getPolicyList();

        Result ret = combine(rules, req, ch, p.getRuleCombiningAlgId(), p.getRuleCombinerParametersArray());
        if (ret.getDecision() != Decision.BYTE_INDETERMINATE && ret.getDecision() != Decision.BYTE_NOT_APPLICABLE) {
            ret.addObligationArray(p.getObligationArray());
        }
        return ret;
    }
*/


    /**
     * todo: finish impl
     *
     * @param p
     * @param req
     * @param ch
     * @return
     */
    public Result evalMatched(PolicySet p, XacmlCtx req, CtxHandler ch) throws XacmlException {
        PolicyProxy[] ps = p.getPolicyProxyArray();

        Result ret = combine(ps, req, ch,
                p.getPolicyCombiningAlgId(), null);

//                p.getPolicyCombinerParametersList().toArray(new PolicyCombinerParameters[1]));

        if (ret.getDecision() != Decision.BYTE_INDETERMINATE
                && ret.getDecision() != Decision.BYTE_NOT_APPLICABLE) {
            ret.addObligationArray(p.getObligationArray());
        }
        return ret;
    }


    private PolicyStore policyStore;
    private PolicyReferenceStore policyReferenceStore;

    public PolicyStore getPolicyStore() {
        return policyStore;
    }

    public void setPolicyStore(PolicyStore policyStore) {
        this.policyStore = policyStore;
    }

    public PolicyReferenceStore getPolicyReferenceStore() {
        return policyReferenceStore;
    }

    public void setPolicyReferenceStore(PolicyReferenceStore policyReferenceStore) {
        this.policyReferenceStore = policyReferenceStore;
    }


    public Result eval(IdReference idRef, XacmlCtx req, CtxHandler ch) throws XacmlException {
        PolicyObject po = null;
        try {
            po = getPolicyReferenceStore().resolve(idRef);
        } catch (Exception e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.

            ResultImpl result = new ResultImpl();
            result.setDecision(Decision.BYTE_INDETERMINATE);
            result.setStatus(new StatusImpl(StatusCode.STRING_PROCESSING_ERR));

        }
        return po.eval(this, req, ch);
    }

    public PolicyCombAlgFactory getPolicyCombAlgFactory() {
        return policyCombAlgFactory;
    }

    public void setPolicyCombAlgFactory(PolicyCombAlgFactory policyCombAlgFactory) {
        this.policyCombAlgFactory = policyCombAlgFactory;
    }

    public Target getTarget(PolicyProxy p) throws
            XacmlException, DuplicateIdReferenceException, UnresolvedIdReferenceException {
        if (p instanceof PolicyObject) return ((PolicyObject) p).getTarget();
        return getPolicyReferenceStore().resolve((IdReference) p).getTarget();


    }

    /**
     * entry point into policy evaluator from outside world
     *
     * @param req
     * @param ch
     * @return
     */
    public Response eval(Request req, CtxHandler ch) throws XacmlException {
        XacmlCtx ctx = new XacmlCtx(req);
        return eval(ctx, ch);
    }


    /**
     * entry point into policy evaluator from outside world
     *
     * @param req
     * @param ch
     * @return
     */
    public Response eval(XacmlCtx req, CtxHandler ch) throws XacmlException {
        Result[] results = new ResultImpl[req.getResourceArray().length];
        for (int i = 0; i < req.getResourceArray().length; i++) {
            req.setCurrentResourceIdx(i);
            results[i] = evalOneResource(req, ch);
        }
        ResponseImpl ret = new ResponseImpl();
        ret.setResultArray(results);
        return ret;
    }

    /**
     * entry point into policy evaluator from outside world
     *
     * @param req
     * @param ch
     * @return
     */
    public Result evalOneResource(XacmlCtx req, CtxHandler ch) throws XacmlException {

        //                List<PolicyObject>
        List pos = null;
        try {
            pos = getPolicyStore().find(req, ch, getTargetMatcher());
        } catch (TargetMatchIndeterminateException x) {
            ResultImpl ret = new ResultImpl();
            ret.setDecision(Decision.BYTE_INDETERMINATE);

            ret.setStatus(x.getStatus());

            if (FeatureDisabler.ENABLE_EXPLAIN && req.getOptions().EXPLAIN) {
                req.addStep("Result Indeterminate because PolicyStore encountered an error while searching for " +
                        "matching policy to the original request.");
                ret.setExplanation(req.getExplanation());
            }

            return ret;
        }
        if (pos == null || pos.size() == 0) {
            /*
            according to xacml 2.0 spec ch 9.1.6. "NotApplicable results":
3703 A result of "NotApplicable" means that the PDP could not locate a policy whose target matched
3704 the information in the decision request.
            */
            ResultImpl ret = new ResultImpl();
            ret.setDecision(Decision.BYTE_NOT_APPLICABLE);
            ret.setStatus(new StatusImpl(StatusCode.INT_OK));


            XacmlException x = new XacmlException("No matching policies found."
                    , XacmlException.ERR_CODE_PROCESSING_NO_MATCHING_POLICIES);
            ret.setXacmlException(x);

            if (logger.isLoggable(Level.FINE)) {
                logger.fine(x.getMessage());
            }

            return ret;
        } else if (pos.size() > 1) {//error when more than one policy found
            ResultImpl ret = new ResultImpl();
            ret.setDecision(Decision.BYTE_INDETERMINATE);
            ret.setStatus(new StatusImpl(StatusCode.INT_PROCESSING_ERR));


            if (logger.isLoggable(Level.FINE)) {
                logger.fine("PolicyStore found multiple policies matching the request, no.: " + pos.size());
            }


            if (FeatureDisabler.ENABLE_EXPLAIN && req.getOptions().EXPLAIN) {
                req.addStep("Result Indeterminate because PolicyStore returned multiple " +
                        "matching policies to the original request.");
                Iterator it = pos.iterator();
                while (it.hasNext()) {
                    PolicyObject po = (PolicyObject) it.next();
                    req.addSibling("Policy id:" + po.getId());
                }
                ret.setExplanation(req.getExplanation());
            }
            return ret;

        }

        //todo: handle multiple results for multiple resources

        Result ret = null;

        if (FeatureDisabler.ENABLE_EXPLAIN && req.getOptions().EXPLAIN) {
            req.addStep("PolicyStore found a matching policy with id:" + ((PolicyObject) pos.get(0)).getId());
        }
        ret = ((PolicyObject) pos.get(0)).evalMatched(this, req, ch);


        if (FeatureDisabler.ENABLE_EXPLAIN && req.getOptions().EXPLAIN) {
            req.addSentence(" Result: " + ret.getDecisionString());
            ret.setExplanation(req.getExplanation());
        }
        return ret;

    }


    /**
     * this method validates syntax of a policy
     *
     * @param policy
     * @throws axescon.xacml.api.XacmlException
     *          if syntax error is found
     */
    public void validate(Policy policy) throws XacmlException {
        try {
            Rule[] rs = policy.getRuleArray();
            for (int i = 0; rs != null && i < rs.length; i++) {
                getRuleEvaluator().validate(rs[i]);
            }
        } catch (XacmlException e) {
            e.setPolicyObject(policy);
            throw e;
        }
    }

    /**
     * this method validates syntax of a policy
     *
     * @param idRef
     * @throws axescon.xacml.api.XacmlException
     *          if syntax error is found
     */
    public void validate(IdReference idRef) throws XacmlException {
        PolicyObject po = null;
        try {
            po = getPolicyReferenceStore().resolve(idRef);
        } catch (Exception e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.

            ResultImpl result = new ResultImpl();
            result.setDecision(Decision.BYTE_INDETERMINATE);
            result.setStatus(new StatusImpl(StatusCode.STRING_PROCESSING_ERR));

        }

        try {
            po.validate(this);
        } catch (XacmlException e) {
            e.setPolicyObject(po);
            throw e;
        }
    }


    /**
     * this method validates syntax of a policySet
     *
     * @param policySet
     * @throws axescon.xacml.api.XacmlException
     *          if syntax error is found
     */
    public void validate(PolicySet policySet) throws XacmlException {
        try {
            PolicyProxy[] ps = policySet.getPolicyProxyArray();
            for (int i = 0; ps != null && i < ps.length; i++) {
                ps[i].validate(this);
            }
        } catch (XacmlException e) {
            e.setPolicyObject(policySet);
            throw e;
        }
    }

    /**
     * this method validates configuration of a policy
     *
     * @throws axescon.xacml.api.XacmlException
     *          if syntax error is found
     */
    public void validate() throws XacmlException {
        if (getPolicyStore() != null) getPolicyStore().validate(this);
        getTargetMatcher().getExpressionEvaluator().getFnFactory().validate();
    }
}
